﻿#include "HumanAssassin.h"
#include "../../MirEnvir/Envir.h"
#include "../../../Shared/Data/Stat.h"
#include "../../../Shared/Functions/Functions.h"
#include "../../MirEnvir/Map.h"
#include "../MapObject.h"
#include "../../../Shared/ServerPackets.h"
#include "../SpellObject.h"
#include "../PlayerObject.h"
#include "../DelayedAction.h"

using namespace Server::MirDatabase;
using namespace Server::MirEnvir;
using namespace Server::MirObjects::Monsters;
namespace S = ServerPackets;

namespace Server::MirObjects::Monsters
{

    HumanAssassin::HumanAssassin(MonsterInfo *info) : MonsterObject(info)
    {
        ExplosionTime = getEnvir()->getTime() + 1000 * 10;
        Summoned = true;
    }

    void HumanAssassin::RefreshBase()
    {
        Stats->Clear();
        Stats->Add(Master->Stats);

        Stats[Stat::HP] = 1500;

        MoveSpeed = 100;
        AttackSpeed = Master->AttackSpeed;
    }

    void HumanAssassin::RefreshAll()
    {
        RefreshBase();

        Stats[Stat::HP] += PetLevel * 20;
        Stats[Stat::MinAC] += PetLevel * 2;
        Stats[Stat::MaxAC] += PetLevel * 2;
        Stats[Stat::MinMAC] += PetLevel * 2;
        Stats[Stat::MaxMAC] += PetLevel * 2;
        Stats[Stat::MinDC] += PetLevel;
        Stats[Stat::MaxDC] += PetLevel;

        if (MoveSpeed < 100)
        {
            MoveSpeed = 100;
        }
        if (AttackSpeed < 100)
        {
            AttackSpeed = 100;
        }

        RefreshBuffs();
    }

    bool HumanAssassin::Walk(MirDirection dir)
    {
        if (!getCanMove())
        {
            return false;
        }

        Point *location = Functions::PointMove(getCurrentLocation(), dir, 2);

        if (!getCurrentMap()->ValidPoint(location))
        {
            return false;
        }

        Cell *cell = getCurrentMap()->GetCell(location);

        bool isBreak = false;

        if (!cell->Objects.empty())
        {
            for (int i = 0; i < cell->Objects.size(); i++)
            {
                MapObject *ob = cell->Objects[i];
                if (!ob->getBlocking())
                {
                    continue;
                }
                isBreak = true;
                break;
            }
        }

        if (isBreak)
        {
            location = Functions::PointMove(getCurrentLocation(), dir, 1);

            if (!getCurrentMap()->ValidPoint(location))
            {
                return false;
            }

            cell = getCurrentMap()->GetCell(location);

            if (!cell->Objects.empty())
            {
                for (int i = 0; i < cell->Objects.size(); i++)
                {
                    MapObject *ob = cell->Objects[i];
                    if (!ob->getBlocking())
                    {
                        continue;
                    }
                    return false;
                }
            }
        }

        getCurrentMap()->GetCell(getCurrentLocation())->Remove(this);

        setDirection(dir);
        RemoveObjects(dir, 1);
        setCurrentLocation(location);
        getCurrentMap()->GetCell(getCurrentLocation())->Add(this);
        AddObjects(dir, 1);

        if (getHidden())
        {
            RemoveBuff(BuffType::Hiding);
        }

        CellTime = getEnvir()->getTime() + 500;
        ActionTime = getEnvir()->getTime() + 300;
        MoveTime = getEnvir()->getTime() + MoveSpeed;
        if (MoveTime > AttackTime)
        {
            AttackTime = MoveTime;
        }

        InSafeZone = getCurrentMap()->GetSafeZone(getCurrentLocation()) != nullptr;

        if (isBreak)
        {
            S::ObjectWalk *tempVar = new S::ObjectWalk();
            tempVar->ObjectID = ObjectID;
            tempVar->Direction = getDirection();
            tempVar->Location = getCurrentLocation();
            Broadcast(tempVar);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
        }
        else
        {
            S::ObjectRun *tempVar2 = new S::ObjectRun();
            tempVar2->ObjectID = ObjectID;
            tempVar2->Direction = getDirection();
            tempVar2->Location = getCurrentLocation();
            Broadcast(tempVar2);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar2' statement was not added since tempVar2 was passed to a method or constructor. Handle memory management manually.
        }


        cell = getCurrentMap()->GetCell(getCurrentLocation());

        for (int i = 0; i < cell->Objects.size(); i++)
        {
            if (cell->Objects[i]->getRace() != ObjectType::Spell)
            {
                continue;
            }
            SpellObject *ob = static_cast<SpellObject*>(cell->Objects[i]);

            ob->ProcessSpell(this);
            //break;
        }

        return true;
    }

    bool HumanAssassin::InAttackRange()
    {
        return getCurrentMap() == getTarget()->getCurrentMap() && Functions::InRange(getCurrentLocation(), getTarget()->getCurrentLocation(), AttackRange);
    }

    void HumanAssassin::ProcessAI()
    {
        if (Dead)
        {
            return;
        }

        ProcessSearch();
        ProcessTarget();

        if (Master != nullptr && dynamic_cast<PlayerObject*>(Master) != nullptr)
        {
            if (getEnvir()->getTime() > ExplosionTime)
            {
                Die();
            }
        }
    }

    void HumanAssassin::ProcessSearch()
    {
        if (getEnvir()->getTime() < SearchTime)
        {
            return;
        }

        SearchTime = getEnvir()->getTime() + SearchDelay;

        //Stacking or Infront of master - Move
        bool stacking = false;

        Cell *cell = getCurrentMap()->GetCell(getCurrentLocation());

        if (!cell->Objects.empty())
        {
            for (int i = 0; i < cell->Objects.size(); i++)
            {
                MapObject *ob = cell->Objects[i];
                if (ob == this || !ob->getBlocking())
                {
                    continue;
                }
                stacking = true;
                break;
            }
        }

        if (getCanMove() && stacking)
        {
            //Walk Randomly
            if (!Walk(getDirection()))
            {
                MirDirection dir = getDirection();

                switch (getEnvir()->Random->Next(3)) // favour Clockwise
                {
                    case 0:
                        for (int i = 0; i < 7; i++)
                        {
                            dir = Functions::NextDir(dir);

                            if (Walk(dir))
                            {
                                break;
                            }
                        }
                        break;
                    default:
                        for (int i = 0; i < 7; i++)
                        {
                            dir = Functions::PreviousDir(dir);

                            if (Walk(dir))
                            {
                                break;
                            }
                        }
                        break;
                }
            }
        }

        if (getTarget() == nullptr || getEnvir()->Random->Next(3) == 0)
        {
            FindTarget();
        }
    }

    void HumanAssassin::ProcessTarget()
    {
        if (getTarget() == nullptr || !getCanAttack())
        {
            return;
        }

        if (InAttackRange())
        {
            Attack();
            return;
        }

        if (getEnvir()->getTime() < ShockTime)
        {
            setTarget(nullptr);
            return;
        }

        int dist = Functions::MaxDistance(getCurrentLocation(), getTarget()->getCurrentLocation());

        if (dist >= AttackRange)
        {
            MoveTo(getTarget()->getCurrentLocation());
        }
    }

    void HumanAssassin::Attack()
    {
        if (AttackDamage >= 500)
        {
            Die();
            return;
        }

        ShockTime = 0;

        if (!getTarget()->IsAttackTarget(this))
        {
            setTarget(nullptr);
            return;
        }


        setDirection(Functions::DirectionFromPoint(getCurrentLocation(), getTarget()->getCurrentLocation()));
        S::ObjectAttack *tempVar = new S::ObjectAttack();
        tempVar->ObjectID = ObjectID;
        tempVar->Direction = getDirection();
        tempVar->Location = getCurrentLocation();
        Broadcast(tempVar);


        ActionTime = getEnvir()->getTime() + 300;
        AttackTime = getEnvir()->getTime() + AttackSpeed;

        int damage = GetAttackPower(Stats[Stat::MinDC], Stats[Stat::MaxDC]);
        AttackDamage += damage;

        if (damage == 0)
        {
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
            return;
        }

        DelayedAction *action = new DelayedAction(DelayedType::Damage, getEnvir()->getTime() + 300, {getTarget(), damage, DefenceType::ACAgility});
        ActionList.push_back(action);

//C# TO C++ CONVERTER TODO TASK: A 'delete action' statement was not added since action was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
    }

    void HumanAssassin::Spawned()
    {
        MonsterObject::Spawned();

        Summoned = false;
    }

    void HumanAssassin::Die()
    {
        if (Dead)
        {
            return;
        }

        ExplosionDie();

        HP = 0;
        Dead = true;

        //DeadTime = Envir.Time + DeadDelay;
        DeadTime = 0;

        S::ObjectDied *tempVar = new S::ObjectDied();
        tempVar->ObjectID = ObjectID;
        tempVar->Direction = getDirection();
        tempVar->Location = getCurrentLocation();
        tempVar->Type = static_cast<unsigned char>(2);
        Broadcast(tempVar);

        if (EXPOwner != nullptr && Master == nullptr && EXPOwner->getRace() == ObjectType::Player)
        {
            EXPOwner->WinExp(getExperience());
        }

        if (Respawn != nullptr)
        {
            Respawn->Count--;
        }

        PoisonList.clear();
        getEnvir()->MonsterCount--;
        getCurrentMap()->MonsterCount--;

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
    }

    void HumanAssassin::ExplosionDie()
    {
        int criticalDamage = getEnvir()->Random->Next(0, 100) <= Stats[Stat::Accuracy] ? Stats[Stat::MaxDC] * 2 : Stats[Stat::MinDC] * 2;
        int damage = (Stats[Stat::MinDC] / 5 + 4 * (getLevel() / 20)) * criticalDamage / 20 + Stats[Stat::MaxDC];

        for (int i = 0; i < 16; i++)
        {
            MirDirection dir = static_cast<MirDirection>(i % 8);
            Point *hitPoint = Functions::PointMove(getCurrentLocation(), dir, (i / 8 + 1));

            if (!getCurrentMap()->ValidPoint(hitPoint))
            {
                continue;
            }

            Cell *cell = getCurrentMap()->GetCell(hitPoint);

            if (cell->Objects.empty())
            {
                continue;
            }


            for (int j = 0; j < cell->Objects.size(); j++)
            {
                MapObject *target = cell->Objects[j];
                switch (target->getRace())
                {
                    case ObjectType::Monster:
                    case ObjectType::Player:
                        //Only targets
                        if (target->IsAttackTarget(static_cast<PlayerObject*>(Master)))
                        {
                            target->Attacked(static_cast<PlayerObject*>(Master), damage, DefenceType::AC, false);
                        }
                        break;
                }
            }
        }
    }

    Packet *HumanAssassin::GetInfo()
    {
        PlayerObject *master = nullptr;
        short weapon = -1;
        short armour = 0;
        unsigned char wing = 0;
        if (Master != nullptr && dynamic_cast<PlayerObject*>(Master) != nullptr)
        {
            master = static_cast<PlayerObject*>(Master);
        }
        if (master != nullptr)
        {
            weapon = master->Looks_Weapon;
            armour = master->Looks_Armour;
            wing = master->Looks_Wings;
        }
        S::ObjectPlayer *tempVar = new S::ObjectPlayer();
        tempVar->ObjectID = ObjectID;
        tempVar->Name = master != nullptr ? master->getName() : getName();
        tempVar->NameColour = NameColour;
        tempVar->Class = master != nullptr ? master->getClass() : MirClass::Assassin;
        tempVar->Gender = master != nullptr ? master->getGender() : MirGender::Male;
        tempVar->Location = getCurrentLocation();
        tempVar->Direction = getDirection();
        tempVar->Hair = master != nullptr ? master->getHair() : static_cast<unsigned char>(0);
        tempVar->Weapon = weapon;
        tempVar->Armour = armour;
        tempVar->Light = master != nullptr ? master->Light : Light;
        tempVar->Poison = CurrentPoison;
        tempVar->Dead = Dead;
        tempVar->Hidden = getHidden();
        tempVar->Effect = SpellEffect::None;
        tempVar->WingEffect = wing;
        tempVar->Extra = false;
        tempVar->TransformType = -1;

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was used in a 'return' or 'throw' statement.
        return tempVar;
    }
}
